-- CIS 552, Mini-project
-- by Mike Gottlieb and Reilly Grant

-- |Module handling all interactions with the user.
module User(gameInfo, playerInfo, promptUser, printHelp,
            clearScreen, getPlayers, notifyDestitute, endGame) where

import Poker (Action(..))
import Control.Monad
import System.Console.Readline
import System.Exit
import List
import Util

import qualified Card
import qualified Player
import qualified Poker

-- |Print vital statistics about the game, like the contents of the pot,
--  and recent actions.
gameInfo :: Poker.GameState -> IO ()
gameInfo s = do putStrLn $ "Pot: " ++ (show $ Poker.completePot s)
                putStrLn $ "Recent actions: " ++ Poker.recentHistory s

-- |Print the user statistics seen at the beginning of their turn.
playerInfo :: Player.T -> [String] -> IO ()
playerInfo p a = do putStr   $ show p
                    putStrLn $ "Actions: " ++ unwords a

-- |Prompt the user to enter an action.
promptUser :: IO Action
promptUser = do maybeInput <- readline "> "
                let input = case maybeInput of
                              Nothing -> "exit"
                              Just s   -> s
--              addHistory input -- information disclosure vulnerability
                action <- parseAction input
                case action of
                  Nothing -> promptUser
                  Just a  -> return a

-- |Displays the help text.
printHelp :: IO ()
printHelp = 
    do mapM_ putStrLn
           ["[b]et n -- Make the amount to play n chips.",
            "[r]aise n -- Raise the amount to play by n chips.",
            "[s]ee -- Match the current bet.",
            "[c]all -- Match the current bet and move to next round.",
            "[f]old -- Give up and lose whatever money you put in the pot.",
            "[ch]eck -- Do nothing.",
            "[d]iscard __cards__ -- Discards cards from your hand.",
            "  __cards is defined as the text representation of your cards",
            "  (e.g. 4H for 4 of Hearts) each separated by a space ",
            "[e]xit -- Quit the current game.",
            "To use a command, enter the full command,",
            "or enter the letter inside of the brackets (ie the b in [b]et)."
           ]

-- |Parse a string from the user into an Action.  Displays proper error messages
--  when there are errors and returns Nothing.
parseAction :: String -> IO (Maybe Action)
parseAction s = 
    let ss = words s 
        parseOne = parseOneNumber $ tail ss in
    if length ss == 0
       then return Nothing
       else do ss' <- case head ss of
                          "b"  -> return "bet"
                          "r"  -> return "raise"
                          "s"  -> return "see"
                          "c"  -> return "call"
                          "ch" -> return "check"
                          "d"  -> return "discard"
                          "e"  -> return "exit"
                          "h"  -> return "help"
                          _    -> return (head ss)
               case ss' of
                  "bet"     -> do bet <- parseOne
                                  case bet of
                                    Nothing -> return Nothing
                                    Just n  -> return $ Just $ Bet n

                  "raise"   -> do raise <- parseOne
                                  case raise of
                                    Nothing -> return Nothing
                                    Just n  -> return $ Just $ Raise n

                  "see"     -> return $ Just See
                  "call"    -> return $ Just Call
                  "fold"    -> return $ Just Fold
                  "check"   -> return $ Just Check

                  "discard" -> do cards <- parseCards $ tail ss
                                  case cards of
                                    Nothing -> return Nothing
                                    Just cs -> return $ Just $ Discard $ nub cs

                  "exit"    -> return $ Just Exit
                  "help"    -> return $ Just Help
                  _         -> do putStrLn $ "Invalid command: " ++ s
                                  return Nothing

-- |Do parsing and validation for an action expecting one number as a parameter.
parseOneNumber :: [String] -> IO (Maybe Int)
parseOneNumber s = 
    if length s /= 1
    then do putStrLn "One parameter expected."
            return Nothing
    else let n = readMaybe $ head s
         in case n of
              Nothing -> do putStrLn $ "Invalid number: " ++ head s
                            return Nothing
              Just n' -> return $ Just n'

-- |Do parsing and validation for an action expecting a list of cards as
--  parameters.
parseCards :: [String] -> IO (Maybe [Card.T])
parseCards [] = return $ Just []
parseCards (c:cs) = 
    do let card = Card.parse c
       case card of 
         Nothing    -> do putStrLn $ "Invalid card: " ++ show c
                          return Nothing
         Just card' -> do cards <- parseCards cs
                          case cards of
                            Nothing -> return Nothing
                            Just cards' -> return $ Just $ card' : cards'

-- |Generate a new list of players from names supplied by the user.
getPlayers :: IO [Player.T]
getPlayers = do x <- readline "Enter player name (enter when done): "
                case x of
                  Just "" -> return []
                  Nothing -> return []
                  Just name -> do rest <- getPlayers
                                  return $ Player.player name : rest

-- |Notify players who are out of the game because they couldn't ante up.
notifyDestitute :: [Player.T] -> IO ()
notifyDestitute [] = return ()
notifyDestitute (p:ps) = if not (Player.inPlay p)
                             then putStr $
                                     Player.name p ++ " does not have enough" ++
                                     " money to ante up.\n"
                             else notifyDestitute ps

-- |Clears the terminal
clearScreen :: IO ()
clearScreen = putStr "\27[H\27[J"

-- |Prints the end of round info and prompts the user to play again.
endGame :: Poker.GameState -> IO Bool
endGame s = 
    let winners = map Player.name $ filter Player.inPlay $ Poker.players s
        reward = Poker.completePot s `div` length winners
        compareFunc x y = compare (Poker.rankHand $ Player.hand y)
                                  (Poker.rankHand $ Player.hand x)
        sorted = sortBy compareFunc $ Poker.players s
        playerOut x = do putStr $ Player.name x ++ ", $"
                         putStr $ (show $ Player.money x) ++ ", "
                         putStr $ unwords $ map Card.showCard (Player.hand x)
                         putStr $
                            ", " ++ (show $ Poker.rankHand $ Player.hand x)
                         if Player.inPlay x
                            then putStrLn ", winner"
                            else putStrLn ""

    -- prints the players, their hands, and ranks of those hands and the winner
    in do sequence_ $ map playerOut sorted
          answer <- readline "Do you want to play another round? (y/n) "
          case answer of
            Nothing  -> return False -- exit
            Just "n" -> return False -- exit
            Just "y" -> return True  -- reset
